/**
 * The Grid class provides functions for generating maps and retrieving/moving entities on a         
 * Grid object.
 * 
 * @author        Zachary Bellido, Ivan Rivera, Adrian Alberto
 * @version       0.03
 * @period        5
 */
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.lang.Object;

public class Grid
{
    private static ArrayList<GridEntity> addQueue = new ArrayList<GridEntity>();
    private static ArrayList<GridEntity> myArlstGridEnt = new ArrayList<GridEntity>();
    
    //Grid generation variables
    static final int width = 30;
    static final int height = 30;
    private static final int minRooms = 4;
    private static final int maxRooms = 7;
    private static final int minRoomSize = 7;
    private static final int maxRoomSize = 20;
    private static final int doorSize = 4;
    public static int numMonsters = 3;
    
    /**
     * The getEntities() method will retrieve all entities regardless of if it is on the grid or not.
     * 
     * @pre     myArlstGridEnt 
     * @param   none
     * @return  ArrayList<GridEntity>    
     * @post    returns an ArrayList consisting of every entity
     */
    public static ArrayList<GridEntity> getEntities()
    {
        return myArlstGridEnt;
    }

    /**
     * The addMoveableEnt() method will add a MovableEnt to the grid
     *
     * @pre     myArlstGridEnt
     * @param   a MovableEnt e
     * @return  none
     * @post    adds the MovableEnt to myArlstGridEnt
     */
    public static void addEnt(GridEntity e)
    {
        myArlstGridEnt.add(e); 
    }
    
    /**
     * The queueEntAddition() adds an entity to the spawning queue
     *
     * @pre     
     * @param   GridEntity 
     * @return  none
     * @post    add Entity to queue
     */
    public static void queueEntAddition(GridEntity e)
    {
        addQueue.add(e); 
    }
    
    /**
     * The emptyQueue() Initializes the gridQueue
     *
     * @pre     
     * @param   none
     * @return  none
     * @post    makes a newQueue list
     */
    public static void emptyQueue()
    {
        for (GridEntity e: addQueue)
        {
            addEnt(e);
        }
        addQueue = new ArrayList<GridEntity>();
    }


    /**
     * Will check if a space is empty
     *
     * @pre     GridEntity, myArlstGridEnt
     * @param   2 integers x and y (coordinates) 
     * @return  true or false
     * @post    returns true if an elements xCoordinate and yCoordinate = the ones in the 
     *          params and if the element = null, otherwise returns false
     */
    public static boolean isEmpty(int x, int y)
    {
        for (GridEntity e : myArlstGridEnt){
            if ((x < 0 | y < 0 | x >= width | y >= height)||(e.getCoordX() == x && e.getCoordY() == y)){
                return false;
            }
        }
        return true;
    }

    /**
     * The moveEntity() method will move an entity to a specified location on the grid.
     *
     * @pre     GridEntity, myArlstGridEnt
     * @param   2 integers x and y (coordinates) and 1 GridEntity ent
     * @return  none
     * @post    set xCoordinate and yCoordinate of the Entity to the ones in the param
     */
    public static void moveEntity(int x, int y, GridEntity ent)
    {
        for (GridEntity e : myArlstGridEnt){
            if (e.equals(ent)){
                e.setCoordX(x);
                e.setCoordY(y);
            }
        }
    }

    /**
     * The getEntAt() method will return the GridEntity at the specified location.
     *
     * @pre     GridEntity, myArlstGridEnt
     * @param   int x, int y    the x and y location on the grid to retrieve an entity from.   
     * @return  GridEntity    the entity at location (x,y) or null
     * @post    returns a GridEntity or null
     */
    public static GridEntity getEntAt(int x, int y)
    {
        for (GridEntity e : myArlstGridEnt)
        {
            if (e.getCoordX() == x && e.getCoordY() == y)
            {
                return e;
            }
        }
        return null;
    }

    /**
     * The generateMap() method will generate a new Map
     *
     * @pre     
     * @param   none
     * @return  
     * @post    
     */
    public static void generateMap()
    {
        //Clear the map.
        for (int i = 0; i < myArlstGridEnt.size(); i++)
        {
            GridEntity ent = myArlstGridEnt.get(i);
            if (!(ent.equals(Runner.player)))
            {
                myArlstGridEnt.remove(i);
                if (!(ent instanceof Money))
                    ent.remove();
                i--;
            }
        }
        
        //Start generation
        int numBoxes = (int) (Math.random() * (double)(maxRooms - minRooms) + (double)minRooms);
        for (int boxIt = 0; boxIt < numBoxes; boxIt++)
        {
            int boxWidth = (int) (Math.random() * (maxRoomSize - minRoomSize) + minRoomSize);
            int boxHeight = (int) (Math.random() * (maxRoomSize - minRoomSize) + minRoomSize);
            int posX = (int) (Math.random() * width-1);
            int posY = (int) (Math.random() * height-1);
            
            //Place item or player in center of box.
            if (boxIt == numBoxes - 1)
                moveEntity(posX, posY, Runner.player);
            else
                new Money(posX, posY);
                
            //Clear space and draw walls of box
            for (int coordX = posX - boxWidth/2; coordX <= posX + boxWidth/2; coordX++)
            {
                for (int coordY = posY - boxHeight/2; coordY <= posY + boxHeight/2; coordY++)
                {
                    //Remove any walls within the square.
                    GridEntity ent = getEntAt(coordX, coordY);
                    if (ent != null && ent instanceof Wall)
                    {
                        getEntAt(coordX, coordY).remove();
                    }
                    //Draw walls at edges
                    if (ent == null && getDistance(coordX, posX) >= boxWidth/2 || getDistance(coordY, posY) >= boxHeight/2)
                    {
                        new Wall('█', coordX, coordY);//█
                    }
                }

            }
            //Cut hallways through entire map
            for (int i = 0; i < myArlstGridEnt.size(); i++)
            {
                GridEntity ent = myArlstGridEnt.get(i);
                if ((ent instanceof Wall) && (getDistance(ent.getCoordX(), posX) < doorSize/2 || getDistance(ent.getCoordY(), posY) < doorSize/2))
                {
                    ent.remove();
                    i--;
                }
            }
           
        }
        
        //Add monsters
        for (int m = 1; m <= numMonsters; m++)
        {
            int pX = (int) (Math.random() * width);
            int pY = (int) (Math.random() * height);
            GridEntity ent = getEntAt(pX, pY);
            //Make sure monster is not spawned too close to player.
            if (ent instanceof FixedEnt || getDistance(pX, Runner.player.getCoordX()) < 7 || getDistance(pY, Runner.player.getCoordY()) < 7)
            {
                m--;
            }
            else
            {
                new Enemy('@', pX, pY);
            }
        }
        //Add projectile pickups
        for (int m = 1; m <= numMonsters/3; m++)
        {
            int pX = (int) (Math.random() * width);
            int pY = (int) (Math.random() * height);
            GridEntity ent = getEntAt(pX, pY);
            if (ent instanceof FixedEnt || getDistance(pX, Runner.player.getCoordX()) < 7 || getDistance(pY, Runner.player.getCoordY()) < 7)
            {
                m--;
            }
            else
            {
                new ProjectilePickup(pX, pY);
            }
        }
    }

    /**
     * Returns absolute value difference between two integers
     * @pre     none  
     * @param   int, int
     * @return  difference
     * @post    none
     */
    private static int getDistance(int a, int b)
    {
        return Math.abs(a - b);
    }

}
